﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.IO;
using Hack.Attributes;

namespace Hack.SystemAPI
{
    [OrginalAuthor(Url = "http://geekswithblogs.net/akraus1/Default.aspx")]
    public class PerfCounterReader : IDisposable
    {
        PerformanceCounter myMemoryCounter;
        const string CategoryNetClrMemory = ".NET CLR Memory";
        const string ProcessId = "Process ID";
        const int ProcessesToTry = 40;

        public PerfCounterReader(int processId)
            : this(Process.GetProcessById(processId))
        {
        }

        string GetInstanceNameForProcess(int instanceCount, Process p)
        {
            string instanceName = Path.GetFileNameWithoutExtension(p.MainModule.FileName);

            if (instanceCount > 0) // Append instance counter
            {
                instanceName += "#" + instanceCount.ToString();
            }

            // Reader .NET CLR Memory Process ID for the given instance to check if
            // it does match our target process
            using (PerformanceCounter counter = new PerformanceCounter(CategoryNetClrMemory, ProcessId,
                   instanceName, true))
            {

                long id = 0;

                try
                {
                    while (true)
                    {
                        var sample = counter.NextSample();
                        id = sample.RawValue;

                        // for some reason it takes quite a while until the counter is
                        // updated with the correct data
                        if (id > 0)
                            break;

                        Thread.Sleep(15);
                    }
                }
                catch (InvalidOperationException)
                {
                    // swallow exceptions from non existing instances we tried to read
                }

                return (id == p.Id) ? instanceName : null;
            }

        }

        string GetManagedPerformanceCounterInstanceName(Process p)
        {
            Func<int, Process, string> PidReader = GetInstanceNameForProcess;
            string instanceName = null;
            AutoResetEvent ev = new AutoResetEvent(false);

            for (int i = 0; i < ProcessesToTry; i++)
            {
                int tmp = i;
                // Since reading the performance counter for every process is
                // very slow we try to speed up our search by reading up to ProcessesToTry
                // in parallel
                PidReader.BeginInvoke(tmp, p, (IAsyncResult res) =>
                {
                    if (instanceName == null)
                    {
                        string correctInstanceName = PidReader.EndInvoke(res);

                        if (correctInstanceName != null)
                        {
                            instanceName = correctInstanceName;
                            ev.Set();
                        }
                    }

                }, null);
            }


            // wait until we got the correct instance name or give up
            if (!ev.WaitOne(20 * 1000))
            {
                throw new InvalidOperationException("Could not get managed performance counter instance name for process " + p.Id);
            }

            return instanceName;
        }

        public PerfCounterReader(Process p)
        {
            string processInstanceName = GetManagedPerformanceCounterInstanceName(p);
            myMemoryCounter = new PerformanceCounter(CategoryNetClrMemory, "# Bytes in all Heaps", processInstanceName);
        }

        public long BytesInAllHeaps
        {
            get
            {
                return myMemoryCounter.NextSample().RawValue;
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            myMemoryCounter.Dispose();
        }

        #endregion
    }
}
